use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier};
use rsa::pkcs8::{DecodePrivateKey, DecodePublicKey, EncodePrivateKey, EncodePublicKey};

pub const AUTHORIZATION: &str = "Authorization";
pub const BEARER_PREFIX: &str = "Bearer ";

pub struct PrivateKey(rsa::RsaPrivateKey);

impl PrivateKey {
    pub fn to_bytes(&self) -> Result<Vec<u8>, anyhow::Error> {
        let doc = self.0.to_pkcs8_der().map_err(anyhow::Error::new)?;

        Ok(doc.as_bytes().to_vec())
    }

    pub fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
        let padding = rsa::Oaep::new::<rsa::sha2::Sha256>();
        self.0
            .decrypt(padding, ciphertext)
            .map_err(anyhow::Error::new)
    }
}

impl TryFrom<&[u8]> for PrivateKey {
    type Error = anyhow::Error;

    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let private_key = rsa::RsaPrivateKey::from_pkcs8_der(value).map_err(anyhow::Error::new)?;
        Ok(Self(private_key))
    }
}

pub struct PublicKey(rsa::RsaPublicKey);

impl PublicKey {
    pub fn to_bytes(&self) -> Result<Vec<u8>, anyhow::Error> {
        let doc = self.0.to_public_key_der().map_err(anyhow::Error::new)?;

        Ok(doc.as_bytes().to_vec())
    }

    pub fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
        let padding = rsa::Oaep::new::<rsa::sha2::Sha256>();
        self.0
            .encrypt(&mut rsa::rand_core::OsRng, padding, plaintext)
            .map_err(anyhow::Error::new)
    }
}

impl TryFrom<&[u8]> for PublicKey {
    type Error = anyhow::Error;
    fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
        let public_key =
            rsa::RsaPublicKey::from_public_key_der(value).map_err(anyhow::Error::new)?;
        Ok(Self(public_key))
    }
}

pub fn new_key_pair() -> Result<(PrivateKey, PublicKey), anyhow::Error> {
    let mut rng = rsa::rand_core::OsRng;
    let private_key = rsa::RsaPrivateKey::new(&mut rng, 2048).map_err(anyhow::Error::new)?;
    let public_key = rsa::RsaPublicKey::from(&private_key);

    Ok((PrivateKey(private_key), PublicKey(public_key)))
}

pub fn hash_password(password: &str) -> Result<String, anyhow::Error> {
    let salt =
        argon2::password_hash::SaltString::generate(&mut argon2::password_hash::rand_core::OsRng);
    let password_hash = argon2::Argon2::default()
        .hash_password(password.as_bytes(), &salt)
        .map_err(anyhow::Error::new)?
        .to_string();
    Ok(password_hash)
}

pub fn verify_password(password: &str, password_hash: &str) -> Result<(), anyhow::Error> {
    let parsed_hash = PasswordHash::new(password_hash).map_err(anyhow::Error::new)?;
    argon2::Argon2::default()
        .verify_password(password.as_bytes(), &parsed_hash)
        .map_err(anyhow::Error::new)
}
